import java.util.HashMap;
import java.util.Scanner;

/**
 * A complete implementation of the Connect Four game with proper documentation,
 * clear naming conventions, and fixed bugs.
 */
public class ConnectFourGame {
    // Constants for piece display
    static final String RED_PIECE = "\uD83D\uDD34";    // Red circle emoji
    static final String BLUE_PIECE = "\uD83D\uDD35";   // Blue circle emoji
    static final String EMPTY_CELL = "\u2B1C";         // White square emoji

    // Mapping of internal codes to display characters
    static HashMap<Integer, String> pieceDisplayMap;

    // Mapping of player colors to internal codes
    static HashMap<String, Integer> playerCodeMap;

    // Game board dimensions
    static final int ROWS = 6;     // Standard Connect Four has 6 rows
    static final int COLUMNS = 7;  // Standard Connect Four has 7 columns

    // Game state variables
    int[][] gameBoard;        // 2D array representing the game board
    String currentPlayer;     // Current player ("red" or "blue")
    Scanner inputScanner;     // Scanner for user input
    int winnerCode;           // 0 = no winner, 1 = red, 2 = blue

    /**
     * Initializes a new Connect Four game.
     * @param startingPlayer The player who goes first ("red" or "blue")
     */
    public ConnectFourGame(String startingPlayer) {
        currentPlayer = startingPlayer;
        gameBoard = new int[ROWS][COLUMNS];
        inputScanner = new Scanner(System.in);

        // Initialize display mappings
        pieceDisplayMap = new HashMap<>();
        pieceDisplayMap.put(0, EMPTY_CELL);  // 0 represents empty cell
        pieceDisplayMap.put(1, RED_PIECE);   // 1 represents red piece
        pieceDisplayMap.put(2, BLUE_PIECE);  // 2 represents blue piece

        // Initialize player code mappings
        playerCodeMap = new HashMap<>();
        playerCodeMap.put("red", 1);
        playerCodeMap.put("blue", 2);
    }

    /**
     * Gets the current game board state.
     * @return 2D array representing the game board
     */
    public int[][] getGameBoard() {
        return gameBoard;
    }

    /**
     * Sets the game board to a new state.
     * @param newBoard The new game board state
     */
    public void setGameBoard(int[][] newBoard) {
        gameBoard = newBoard;
    }

    /**
     * Gets the current player.
     * @return "red" or "blue"
     */
    public String getCurrentPlayer() {
        return currentPlayer;
    }

    /**
     * Main game loop that controls the game flow until completion.
     */
    public void playGame() {
        while (!isGameOver()) {
            displayBoard();
            promptPlayerTurn();
            int selectedColumn = getValidColumnInput();
            dropPiece(selectedColumn);
            checkGameStatus();
            switchPlayer();
        }

        // Game over - display final board and result
        displayBoard();
        if (winnerCode > 0) {
            System.out.println("Player " + pieceDisplayMap.get(winnerCode) + " wins the game!");
        } else {
            System.out.println("The game ended in a draw.");
        }
    }

    /**
     * Displays the current state of the game board.
     */
    public void displayBoard() {
        for (int i = 0; i < gameBoard.length; i++) {
            for (int j = 0; j < gameBoard[i].length; j++) {
                System.out.print(pieceDisplayMap.get(gameBoard[i][j]) + " ");
            }
            System.out.println();
        }
        // Display column numbers for reference
        System.out.println("1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣");
    }

    /**
     * Prompts the current player to take their turn.
     */
    public void promptPlayerTurn() {
        if (currentPlayer.equals("red")) {
            System.out.println(RED_PIECE + " player's turn:");
        } else {
            System.out.println(BLUE_PIECE + " player's turn:");
        }
    }

    /**
     * Switches the current player.
     */
    public void switchPlayer() {
        currentPlayer = currentPlayer.equals("red") ? "blue" : "red";
    }

    /**
     * Gets valid column input from the player (1-7).
     * @return The selected column number (1-7)
     */
    public int getValidColumnInput() {
        while (!inputScanner.hasNextInt()) {
            System.out.println("Please enter an integer!");
            inputScanner.nextLine();
        }
        int column = inputScanner.nextInt();

        // Validate column range
        while (column < 1 || column > COLUMNS) {
            System.out.println("Please enter a number between 1 and 7!");
            column = inputScanner.nextInt();
        }

        // Check if column has available space
        while (!isColumnAvailable(column)) {
            System.out.println("Column is full! Please choose another column!");
            column = inputScanner.nextInt();
        }

        return column;
    }

    /**
     * Checks if a column has available space for a new piece.
     * @param columnNumber The column to check (1-7)
     * @return true if the column has space, false if full
     */
    public boolean isColumnAvailable(int columnNumber) {
        int columnIndex = columnNumber - 1;
        return gameBoard[0][columnIndex] == 0;
    }

    /**
     * Drops a piece into the specified column.
     * @param columnNumber The column where the piece should be dropped (1-7)
     */
    public void dropPiece(int columnNumber) {
        assert isColumnAvailable(columnNumber) : "Invalid move - column is full";
        int columnIndex = columnNumber - 1;

        // Find the lowest empty row in the column
        for (int i = ROWS - 1; i >= 0; i--) {
            if (gameBoard[i][columnIndex] == 0) {
                gameBoard[i][columnIndex] = playerCodeMap.get(currentPlayer);
                return;
            }
        }
    }

    /**
     * Checks if the game has ended (win or draw).
     * @return true if the game is over, false otherwise
     */
    public boolean isGameOver() {
        return checkHorizontalWin() || checkVerticalWin() || checkDiagonalWin() || isBoardFull();
    }

    /**
     * Checks the game status and updates winnerCode if there's a winner.
     */
    public void checkGameStatus() {
        // The win conditions are checked in isGameOver()
        // This method is kept for potential future extensions
    }

    /**
     * Checks for horizontal wins (4 consecutive pieces in a row).
     * @return true if a horizontal win is detected
     */
    public boolean checkHorizontalWin() {
        for (int i = 0; i < ROWS; i++) {
            if (checkConsecutivePieces(gameBoard[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks for vertical wins (4 consecutive pieces in a column).
     * @return true if a vertical win is detected
     */
    public boolean checkVerticalWin() {
        int[] columnPieces = new int[ROWS];
        for (int j = 0; j < COLUMNS; j++) {
            for (int i = 0; i < ROWS; i++) {
                columnPieces[i] = gameBoard[i][j];
            }
            if (checkConsecutivePieces(columnPieces)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks for diagonal wins (4 consecutive pieces diagonally).
     * @return true if a diagonal win is detected
     */
    public boolean checkDiagonalWin() {
        // Check diagonals from bottom-left to top-right
        for (int i = 0; i <= ROWS - 4; i++) {
            for (int j = 0; j <= COLUMNS - 4; j++) {
                if (checkSingleDiagonal(i, j)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks a specific diagonal for 4 consecutive pieces.
     * @param startRow Starting row index
     * @param startCol Starting column index
     * @return true if 4 consecutive pieces found in diagonal
     */
    private boolean checkSingleDiagonal(int startRow, int startCol) {
        int[] diagonal1 = new int[4];
        int[] diagonal2 = new int[4];

        for (int k = 0; k < 4; k++) {
            diagonal1[k] = gameBoard[startRow + k][startCol + k];
            diagonal2[k] = gameBoard[startRow + k][startCol + 3 - k];
        }

        return checkConsecutivePieces(diagonal1) || checkConsecutivePieces(diagonal2);
    }

    /**
     * Checks if the board is completely full (draw condition).
     * @return true if the board is full, false otherwise
     */
    public boolean isBoardFull() {
        for (int j = 0; j < COLUMNS; j++) {
            if (gameBoard[0][j] == 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * Checks an array of pieces for 4 consecutive identical pieces.
     * @param pieces Array of piece codes to check
     * @return true if 4 consecutive pieces found, false otherwise
     */
    public boolean checkConsecutivePieces(int[] pieces) {
        int consecutiveCount = 0;
        for (int i = 0; i < pieces.length; i++) {
            if (pieces[i] == 0) {
                consecutiveCount = 0;  // Reset for empty cells
            } else if (i > 0 && pieces[i] == pieces[i - 1]) {
                consecutiveCount++;
            } else {
                consecutiveCount = 1;
            }

            // Check for win condition
            if (consecutiveCount >= 4) {
                winnerCode = pieces[i];
                return true;
            }
        }
        winnerCode = 0;
        return false;
    }

    /**
     * Main method to start the game.
     * @param args Command line arguments (not used)
     */
    public static void main(String[] args) {
        ConnectFourGame game = new ConnectFourGame("blue");
        game.playGame();
    }
}